%% gui: 图形用户界面
function gui
    global appRoot;
    appRoot = mfilename('fullpath');
    filesepIndexs = strfind(appRoot, filesep);
    appRoot = appRoot(1:filesepIndexs(end));

    % 窗口尺寸
    gWidth = 900;
    gHeight = 500;

    % 组件尺寸
    itemHeight = 25;
    itemHeightSmall = 20;
    itemHeightLarge = 30;
    itemWidth = 150;

    % 间隙尺寸
    gap = 20;
    gapSmall = 10;
    gapLarge = 30;

    % 特定组件的尺寸
    selectorHeight = 50;
    selectorNum = 7;
    % panel 高度
    panelHeight = gHeight - gap * 2;
    panelHeightLeftUp = panelHeight / 2;
    panelHeightLeftDown = panelHeight / 2 - gap;
    panelHeightRightUp = panelHeight * 0.6;
    panelHeightRightDown = panelHeight * 0.4 - gap;
    % 左侧 panel 宽度
    panelLeftWidth = 600;
    % 右侧 panel 宽度
    panelRightWidth = gWidth - panelLeftWidth - gap * 3;

    addpath('./functions');
    addpath('./assistant');
    addpath('./inversion');
    addpath('./optimization');

    gui = figure();
    screenSize = get(0,'ScreenSize');
    set(gui, ...
        'NumberTitle','off', ...
        'Name','Rayinvr', ...
        'MenuBar','none', ...
        'ToolBar','none', ...
        'Position',[(screenSize(3)-gWidth)/2,(screenSize(4)-gHeight)/2,gWidth,gHeight] ...
    );

    panelLeftUp = uipanel( ...
        'Parent',gui, ...
        'Title','1. 输入', ...
        'Unit','pixels', ...
        'Position',[gap,panelHeightLeftDown+gap*2,panelLeftWidth,panelHeightLeftUp] ...
    );

    panelLeftDown = uipanel( ...
        'Parent',gui, ...
        'Title','2. 预处理', ...
        'Unit','pixels', ...
        'Position',[gap,gap,panelLeftWidth,panelHeightLeftDown] ...
    );

    panelRightUp = uipanel( ...
        'Parent',gui, ...
        'Title','3. 正演', ...
        'Unit','pixels', ...
        'Position',[panelLeftWidth+gap*2,panelHeightRightDown+gap*2,panelRightWidth,panelHeightRightUp] ...
    );

    panelRightDown = uipanel( ...
        'Parent',gui, ...
        'Title','4. 反演', ...
        'Unit','pixels', ...
        'Position',[panelLeftWidth+gap*2,gap,panelRightWidth,panelHeightRightDown] ...
    );

    panelSelectFolder = uipanel( ...
        'Parent',panelLeftUp, ...
        'Title','选择输入文件所在目录', ...
        'BorderType','none', ...
        'Unit','pixels', ...
        'Position',[gapSmall,panelHeightLeftUp-gapLarge-selectorHeight,panelLeftWidth-gapSmall*2,selectorHeight] ...
    );

    editSelectFolder = uicontrol(...
        'Parent',panelSelectFolder, ...
        'Style','edit', ...
        'Callback',@editSelectFolderCallback, ...
        'HorizontalAlignment','left', ...
        'Unit','normalized', ...
        'Position',[0.01,0.1,0.85,0.8] ...
    );

    btnSelectFolder = uicontrol(...
        'Parent',panelSelectFolder, ...
        'Style','pushbutton', ...
        'String','选择', ...
        'Callback',@btnSelectFolderCallback, ...
        'Unit', 'normalized', ...
        'Position',[0.87,0.1,0.12,0.8] ...
    );

    panelSelectRin = uipanel( ...
        'Parent',panelLeftUp, ...
        'Title','选择 r.in 文件（自动）', ...
        'BorderType','none', ...
        'Unit','pixels', ...
        'Position',[gapSmall,panelHeightLeftUp-gapLarge-gapSmall-selectorHeight*2,panelLeftWidth-gapSmall*2,selectorHeight] ...
    );

    editSelectRin = uicontrol(...
        'Parent',panelSelectRin, ...
        'Style','edit', ...
        'HorizontalAlignment', 'left', ...
        'Unit', 'normalized', ...
        'Position',[0.01,0.1,0.85,0.8] ...
    );

    btnSelectRin = uicontrol(...
        'Parent',panelSelectRin, ...
        'Style','pushbutton', ...
        'String','选择', ...
        'Callback',@btnSelectRinCallback, ...
        'Unit', 'normalized', ...
        'Position',[0.87,0.1,0.12,0.8] ...
    );

    panelSelectVin = uipanel( ...
        'Parent',panelLeftUp, ...
        'Title','选择 v.in 文件（自动）', ...
        'BorderType','none', ...
        'Unit','pixels', ...
        'Position',[gapSmall,panelHeightLeftUp-gapLarge-gapSmall*2-selectorHeight*3,panelLeftWidth-gapSmall*2,selectorHeight] ...
    );

    editSelectVin = uicontrol(...
        'Parent',panelSelectVin, ...
        'Style','edit', ...
        'HorizontalAlignment', 'left', ...
        'Unit', 'normalized', ...
        'Position',[0.01,0.1,0.85,0.8] ...
    );

    btnSelectVin = uicontrol(...
        'Parent',panelSelectVin, ...
        'Style','pushbutton', ...
        'String','选择', ...
        'Callback',@btnSelectVinCallback, ...
        'Unit', 'normalized', ...
        'Position',[0.87,0.1,0.12,0.8] ...
    );

    btnAssistant = uicontrol(...
        'Parent', panelLeftDown, ...
        'Style', 'pushbutton', ...
        'String', '预处理工具箱', ...
        'TooltipString', '编辑v.in等输入文件', ...
        'Callback', @btnAssistantCallback, ...
        'Unit', 'pixels', ...
        'Position', [gap,panelHeightLeftDown-gapLarge-itemHeightLarge,itemWidth,itemHeightLarge] ...
    );

    btnOptimize = uicontrol(...
        'Parent', panelLeftDown, ...
        'Style', 'pushbutton', ...
        'String', '基因算法优化', ...
        'TooltipString', '利用基因算法自动优化泊松比数组', ...
        'Callback', @btnOptimizeCallback, ...
        'Unit', 'pixels', ...
        'Position', [gap,panelHeightLeftDown-2*gapLarge-2*itemHeightLarge,itemWidth,itemHeightLarge] ...
    );

    checkboxOde = uicontrol(...
        'Parent', panelRightUp, ...
        'Style', 'checkbox', ...
        'String', '调用 Matlab ODE 工具箱', ...
        'Unit', 'pixels', ...
        'Position', [gap,panelHeightRightUp-itemHeight-gapLarge,500,itemHeight] ...
    );

    checkboxTimer = uicontrol(...
        'Parent', panelRightUp, ...
        'Style', 'checkbox', ...
        'String', '开启耗时统计', ...
        'Unit', 'pixels', ...
        'Position', [gap,panelHeightRightUp-itemHeight*2-gapLarge-gapSmall,500,itemHeight] ...
    );

    btnCalc = uicontrol(...
        'Parent', panelRightUp, ...
        'Style', 'pushbutton', ...
        'String', '仅计算', ...
        'Callback', @btnCalcCallback, ...
        'Unit', 'pixels', ...
        'Position', [gap,panelHeightRightUp-itemHeight*2-gapLarge-gapSmall*2-itemHeightLarge,itemWidth,itemHeightLarge] ...
    );

    btnPlot = uicontrol(...
        'Parent', panelRightUp, ...
        'Style', 'pushbutton', ...
        'String', '仅绘图', ...
        'Callback', @btnPlotCallback, ...
        'Unit', 'pixels', ...
        'Position', [gap,panelHeightRightUp-itemHeight*2-gapLarge-gapSmall*3-itemHeightLarge*2,itemWidth,itemHeightLarge] ...
    );

    btnRun = uicontrol(...
        'Parent',panelRightUp, ...
        'Style','pushbutton', ...
        'String','计算并绘图', ...
        'Callback',@btnRunCallback, ...
        'Unit','pixels', ...
        'Position',[gap,panelHeightRightUp-itemHeight*2-gapLarge-gapSmall*4-itemHeightLarge*3,itemWidth,itemHeightLarge] ...
    );

    textWidth = 100;
    textIter = uicontrol(...
        'Parent',panelRightDown, ...
        'Style','text', ...
        'HorizontalAlignment','left', ...
        'String','当前已迭代次数：', ...
        'Unit','pixels', ...
        'Position',[gap,panelHeightRightDown-itemHeight-gapLarge-6,textWidth,itemHeight] ...
    );

    textCurrentIter = uicontrol(...
        'Parent',panelRightDown, ...
        'Style','text', ...
        'HorizontalAlignment','left', ...
        'String','', ...
        'Unit','pixels', ...
        'Position',[gap+textWidth,panelHeightRightDown-itemHeight-gapLarge-6,30,itemHeight] ...
    );

    % popupIter = uicontrol(...
    %   'Parent',panelRightDown, ...
    %   'Style','popupmenu', ...
    %   'String',sprintf('1\n2\n3\n4\n5\n6\n7\n8\n9\n10'), ...
    %   'Value', 4, ...
    %   'Unit','pixels', ...
    %   'Position',[gap+textWidth,panelHeightRightDown-itemHeight-gapLarge,50,itemHeight] ...
    % );

    btnRefresh = uicontrol(...
        'Parent',panelRightDown, ...
        'Style','pushbutton', ...
        'String','重新检测', ...
        'Callback',@btnRefreshCallback, ...
        'Unit','pixels', ...
        'Position',[gap,panelHeightRightDown-itemHeight-itemHeightLarge-gapLarge-gapSmall,itemWidth,itemHeightLarge] ...
    );

    btnInverse = uicontrol(...
        'Parent',panelRightDown, ...
        'Style','pushbutton', ...
        'String','反演', ...
        'TooltipString','在当前迭代次数的基础上运行反演',...
        'Callback',@btnInverseCallback, ...
        'Unit','pixels', ...
        'Position',[gap,panelHeightRightDown-itemHeight-itemHeightLarge*2-gapLarge-gapSmall*2,itemWidth,itemHeightLarge] ...
    );

    % 设置字体
    setFontSize({panelSelectFolder,editSelectFolder,btnSelectFolder,panelSelectVin,editSelectVin,btnSelectVin,...
        panelSelectRin,editSelectRin,btnSelectRin,checkboxOde,checkboxTimer,textIter,textCurrentIter},9);
    setFontSize({panelLeftUp,panelLeftDown,panelRightUp,panelRightDown,btnAssistant,...
        btnOptimize,btnCalc,btnPlot,btnRun,btnRefresh,btnInverse},10);

    function setFontSize(handles, fontSize)
        for ii = 1:length(handles)
            set(handles{ii},'FontName','Microsoft YaHei','FontSize',fontSize);
        end
    end

    % 保存变量到 mat 文件中
    function fun_saveVar(varName)
        if exist('history_gui.mat','file')
            save('history_gui.mat',varName,'-append');
        else
            save('history_gui.mat',varName);
        end
    end

    % 检查目录
    function valid = fun_checkPath(p)
        valid = true;
        if isempty(p)
            errordlg('请选择输入文件目录','输入错误');
            valid = false;
        end
    end

    % 获取当前反演已迭代次数，即检查最新 iteration 目录
    function iteration = fun_getCurrentIteration(pathIn_)
        iteration = 0;
        subdirs = dir(pathIn_);
        for ii = 1:length(subdirs)
            subdir = subdirs(ii);
            % 如果不是目录则跳过
            if (isequal(subdir.name, '.' ) || isequal(subdir.name, '..') || ~subdir.isdir)
                continue;
            end
            indexs = strfind(subdir.name, 'iteration');
            if ~isempty(indexs) && indexs(1) == 1
                iter = str2num(subdir.name(10:end));
                iteration = max(iter, iteration);
            end
        end
    end

    % 自动设置 v.in 文件的位置
    % 如果还未反演，设为 pathIn/v.in；如果反演到第 x 轮，设为 pathIn/iterationx/v.in
    function fun_autoSetVin(pathIn, iteration)
        if iteration > 0
            set(editSelectVin,'String',fullfile(pathIn,['iteration',num2str(iteration)],'v.in'));
        else
            set(editSelectVin,'String',fullfile(pathIn,'v.in'));
        end
    end

    % 刷新依赖 pathIn 的变量
    function iteration = fun_pathInChanged(pathIn)
        % 依据当前目录计算反演迭代次数
        iteration = fun_getCurrentIteration(pathIn);
        set(textCurrentIter,'String',iteration);

        % 自动设置 v.in 目录
        fun_autoSetVin(pathIn, iteration);
        % 自动设置 r.in 目录
        set(editSelectRin,'String',fullfile(pathIn,'r.in'));

        % 判断是否可以进行反演
        if fun_canInverse(pathIn, iteration)
            set(btnInverse,'Enable','on','String','反演');
        else
            set(btnInverse,'Enable','off','String','需要正演');
        end
    end

    % 检测是否可以运行反演(以防连续多次运行反演导致错误结果)
    % 检测当前 i.out 文件是否与上一轮反演所用的 i.out 相同即可
    function flag = fun_canInverse(pathIn, iteration)
        flag = true;
        if iteration == 0, return; end
        ioutCurr = fullfile(pathIn,'output','i.out');
        ioutPrev = fullfile(pathIn,['iteration',num2str(iteration-1)],'i.out');
        text1 = fileread(ioutCurr);
        text2 = fileread(ioutPrev);
        equal = strcmp(text1,text2);
        flag = ~equal;
    end

    % 界面逻辑
    pathIn = '';
    pathVin = '';
    pathRin = '';
    currentIteration = 0;
    if exist('history_gui.mat','file')
        load('history_gui.mat','pathIn');
        % load('history_gui.mat','pathVin');
    end
    set(editSelectFolder,'String',pathIn);
    currentIteration = fun_pathInChanged(pathIn);

    %% callbacks

    % 选择输入文件所在目录
    function btnSelectFolderCallback(hObject, eventdata, handles)
    % hObject    handle to btnSelectFolder (see GCBO)
    % eventdata  reserved - to be defined in a future version of MATLAB
    % handles    structure with handles and user data (see GUIDATA)
        startPath = get(editSelectFolder,'String');
        pathIn = uigetdir(startPath,'选择输入文件所在目录');
        % 如果成功选择目录
        if pathIn
            set(editSelectFolder,'String',pathIn);
            fun_saveVar('pathIn');
            currentIteration = fun_pathInChanged(pathIn);
        end
    end

    % 手动修改文字框并回车
    function editSelectFolderCallback(hObject, eventdata, handles)
        pathIn = get(hObject,'String');
        fun_saveVar('pathIn');
        currentIteration = fun_pathInChanged(pathIn);
    end

    % 选择 v.in 文件
    function btnSelectVinCallback(hObject, eventdata, handles)
        [name, dirname] = uigetfile('*.in','选择 v.in 文件');
        % 如果用户未选择目录而直接取消对话框
        if name
            pathVin = fullfile(dirname, name);
            set(editSelectVin,'String',pathVin);
            % fun_saveVar('pathVin');
        end
    end

    % 选择 r.in 文件
    function btnSelectRinCallback(hObject, eventdata, handles)
        [name, dirname] = uigetfile('*.in','选择 r.in 文件');
        % 如果用户未选择目录而直接取消对话框
        if name
            pathRin = fullfile(dirname, name);
            set(editSelectRin,'String',pathRin);
        end
    end

    % 启动预处理工具箱
    function btnAssistantCallback(hObject,eventdata,handles)
        assistant_gui();
    end

    % 启动优化工具箱
    function btnOptimizeCallback(hObject,eventdata,handles)
        pathIn = get(editSelectFolder,'String');
        ok = fun_checkPath(pathIn);
        if ~ok, return; end
        pathVin = get(editSelectVin,'String');
        pathRin = get(editSelectRin,'String');
        if isempty(pathRin)
            pathRin = fullfile(pathIn, 'v.in');
        end
        if isempty(pathVin)
            pathVin = fullfile(pathIn, 'v.in');
        end
        isUseOde = get(checkboxOde,'Value');

        [final_x, final_val] = start_optimize(pathIn, pathRin, pathVin, isUseOde);
    end

    % 运行按钮
    function btnRunCallback(hObject,eventdata,handles)
        pathIn = get(editSelectFolder,'String');
        ok = fun_checkPath(pathIn);
        if ~ok, return; end
        pathRin = get(editSelectRin,'String');
        if isempty(pathRin)
            pathRin = fullfile(pathIn, 'v.in');
        end
        pathVin = get(editSelectVin,'String');
        if isempty(pathVin)
            pathVin = fullfile(pathIn, 'v.in');
        end

        isUseOde = get(checkboxOde,'Value');
        isTimeCounting = get(checkboxTimer,'Value');
        if isTimeCounting
            profile on;
            [RMS, CHI] = start_rayinvr(pathIn, pathRin, pathVin, isUseOde);
            profile off; profile viewer;
        else
            [RMS, CHI] = start_rayinvr(pathIn, pathRin, pathVin, isUseOde);
        end

        % 正演结束后，释放反演按钮的禁用状态
        set(btnInverse,'Enable','on','String','反演');
    end

    % 刷新当前反演迭代次数
    function btnRefreshCallback(hObject,eventdata,handles)
        pathIn = get(editSelectFolder,'String');
        ok = fun_checkPath(pathIn);
        if ~ok, return; end
        currentIteration = fun_pathInChanged(pathIn);
    end

    % 反演按钮
    function btnInverseCallback(hObject,eventdata,handles)
        pathIn = get(editSelectFolder,'String');
        ok = fun_checkPath(pathIn);
        if ~ok, return; end
        fun_dmplstsqr(pathIn, currentIteration+1);

        % 反演结束后，刷新当前反演迭代次数
        currentIteration = fun_pathInChanged(pathIn);

        % 反演结束后需要运行一次正演才能继续反演，所以先禁用反演按钮
        set(hObject,'Enable','off','String','需要正演');
    end

end
